//=============================================================================
// System  : MatchKit
// File    : MatchBag.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using System.Collections.Generic;
#if EX
using System.Linq.Expressions;
#endif
using System.Reflection;
using System.Runtime.Serialization;

namespace MatchKit.ObjectMapping
{
	/// <inheritdoc cref="T:MatchKit.ObjectMapping.IMatchBag" select="summary" />
	/// <threadsafety static="true" instance="false" />
	/// <serialization binary="true" contract="true" />
	/// <conceptualLink target="7e345cd8-5aaa-4616-a3c9-abcd0544dabc" />
	/// <conceptualLink target="0a06b2a9-5f58-409a-ba62-639cfd77bca7" />
	#if DC
	[DataContract]
	#endif
	#if SZ
	[Serializable]
	#endif
	public class MatchBag : IMatchBag
	{
		#if DC
		[DataContract]
		#endif
		#if SZ
		[Serializable]
		#endif
		sealed class EntryKey
		{
			#if DC
			[DataMember]
			#endif
			public readonly object Instance;
			#if DC
			[DataMember]
			#endif
			public readonly MemberInfo Member;
			#if DC
			[DataMember]
			#endif
			private readonly Type InstanceType;

			public EntryKey(object instance, MemberInfo member)
			{
				Instance = instance;
				Member = member;
				InstanceType = instance.GetType();
			}

			public override bool Equals(object obj)
			{
				var entry = obj as EntryKey;
				if (entry == null)
					return false;
				return object.ReferenceEquals(entry.Instance, Instance)
					&& entry.Member == Member;
			}

			public override int GetHashCode()
			{
				var h = InstanceType.GetHashCode();
				h ^= Member.GetHashCode();
				return h;
			}
		}

		/// <summary>
		/// Gets or sets the dictionary of matched members.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		private Dictionary<EntryKey, Match> _entries = new Dictionary<EntryKey, Match>();

		/// <inheritdoc />
		public void Clear()
		{
			_entries.Clear();
		}

		/// <inheritdoc />
		public void Trace(object instance, MemberInfo member, Match match)
		{
			if (instance == null)
				throw new ArgumentNullException("instance", "instance cannot be null");
			if (member == null)
				throw new ArgumentNullException("member", "member cannot be null");
			if (match == null)
				throw new ArgumentNullException("match", "match cannot be null");
			if (!match.Success)
				throw new ArgumentNullException("match", "match must be success");
			_entries[new EntryKey(instance, member)] = match;
		}

		/// <inheritdoc />
		public Match GetMatch(object instance, MemberInfo member)
		{
			if (instance == null)
				throw new ArgumentNullException("instance", "instance cannot be null");
			if (member == null)
				throw new ArgumentNullException("member", "member cannot be null");
			Match entry;
			if (!_entries.TryGetValue(new EntryKey(instance, member), out entry))
				return null;
			else
				return entry;
		}

		/// <inheritdoc />
		public Match GetMatch(object instance, string memberName)
		{
			if (instance == null)
				throw new ArgumentNullException("instance", "instance cannot be null");
			if (memberName == null)
				throw new ArgumentNullException("memberName", "memberName cannot be null");

			var members = instance.GetType().GetMember(memberName, BindingFlags.NonPublic 
				| BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

			if (members == null || memberName.Length == 0)
				throw new ArgumentException("memberName does not exists", "memberName");

			return GetMatch(instance, members[0]);
		}

		#if EX
		/// <inheritdoc />
		public Match GetMatch<TInstance, TMember>(TInstance instance, Expression<Func<TInstance, TMember>> memberReference)
		{
			if (instance == null)
				throw new ArgumentNullException("instance", "instance cannot be null");
			if (memberReference == null)
				throw new ArgumentNullException("memberReference", "memberReference cannot be null");

			var body = memberReference.Body;
			var memberExpr = body as MemberExpression;

			if (memberExpr == null)
				throw new ArgumentException("memberReference must contains a MemberExpression body", "memberReference");

			if (memberExpr.Member.GetAttribute<MatchableMemberAttribute>() == null)
				throw new ArgumentException("memberReference must reference a matchable member", "memberReference");

			var field = memberExpr.Member as FieldInfo;
			var property = memberExpr.Member as PropertyInfo;

			if (field == null && property == null)
				throw new ArgumentException("body must reference a field or a property", "body");

			return GetMatch(instance, memberExpr.Member);
		}
		#endif
	}
}
